/**
 * Javascript 画布工具类
 * @param {*} canvasContainerEl 画布容器元素
 * @param {*} canvasProps 配置项 {
      id = "dynamic_canvas_el",
      width = 375,
      height = 750,
      radius = 0,
      dpr = 2,
      backgroundColor = null,
    }
 * @returns
 */

export default class Draw {
  constructor(canvasContainerEl, canvasProps = {}) {
    this.canvas = null;
    this.ctx = null;

    if (!canvasContainerEl) {
      console.error("[canvasContainerEl] is undefined");
      return;
    }

    this.canvasContainerEl = canvasContainerEl;
    this.cProps = canvasProps;
    this.init();
  }

  /**
   * 初始化画布
   */
  init() {
    let {
      id = "dynamic_canvas_el",
      width = 375,
      height = 750,
      radius = 0,
      dpr = 2,
      backgroundColor = null,
    } = this.cProps;

    this.dpr = dpr;
    this.width = width;
    this.height = height;

    let canvasCanvasEl = document.getElementById(id);
    if (canvasCanvasEl) {
      canvasCanvasEl.remove();
    }
    let c = document.createElement("canvas");
    c.id = id;
    c.width = width * dpr;
    c.height = height * dpr;
    c.style.width = `${width}px`;
    c.style.height = `${height}px`;
    c.style.borderRadius = `${radius}px`;
    c.style.display = "block";
    this.canvasContainerEl && this.canvasContainerEl.appendChild(c);

    this.ctx = c.getContext("2d");
    this.ctx.scale(dpr, dpr); //整体放大dpr倍，解决模糊问题

    // 填充背景颜色
    if (backgroundColor) {
      this.ctx.fillStyle = backgroundColor;
      this.ctx.fillRect(0, 0, width, height);
    }

    this.canvas = c;
  }

  /**
   * 测量指定大小的文案的长度
   * @param {需要测量的文字内容} text
   * @param {设置的文字大小} fontSize
   */
  measureText(text, fontSize) {
    this.ctx.save();
    this.ctx.font = fontSize + "px 黑体";
    const metrics = this.ctx.measureText(text);
    this.ctx.restore();
    const width = metrics.width;
    return Math.ceil(width);
  }

  /**
   * 绘制矩形
   * @param {左上角的横坐标} x
   * @param {左上角的纵坐标} y
   * @param {矩形宽度} width
   * @param {矩形高度} height
   * @param {填充颜色} color
   */
  drawRect({ x, y, width, height, color = "#000000" }) {
    this.ctx.beginPath();
    this.ctx.fillStyle = color;
    this.ctx.fillRect(x, y, width, height);
    this.ctx.closePath();
  }

  /**
   * 绘制圆形
   * @param {圆心横坐标} x
   * @param {圆心纵坐标} y
   * @param {半径} r
   * @param {圆弧起始角度，默认0} sAngle
   * @param {圆弧终止角度，默认2} eAngle
   * @param {边框颜色} borderColor
   * @param {边框宽度} borderWidth
   */
  drawCircle({
    x,
    y,
    r,
    sAngle = 0,
    eAngle = 2,
    borderColor = "#000000",
    counterclockwise = false,
    borderWidth = 1,
  }) {
    this.ctx.beginPath();
    this.ctx.strokeStyle = borderColor;
    this.ctx.lineWidth = borderWidth;
    this.ctx.arc(x, y, r, sAngle, eAngle * Math.PI, counterclockwise);
    this.ctx.fill();
    this.ctx.closePath();
    this.ctx.stroke();
  }

  /**
   * 绘制图片
   * @param {图片地址} url
   * @param {左上角的横坐标} x
   * @param {左上角的纵坐标} y
   * @param {图片宽度} width
   * @param {图片高度} height
   */
  drawImage({ url, x, y, width, height }, isSave = true) {
    if (isSave) {
      this.ctx.save();
    }
    let img = new Image();
    img.crossOrigin = "anonymous"; // 允许跨域
    img.src = url;
    img.onload = () => {
      this.ctx.drawImage(img, x, y, width, height);
      this.ctx.restore();
    };
  }

  /**
   * 绘制居中文本
   * @param {文本内容} text
   * @param {文本宽度} width
   * @param {文本颜色} color
   * @param {文本大小，默认16px} fontSize（跟font拼接成ctx.font的值）
   * @param {字体样式的属性字符串} font（跟fontSize拼接成ctx.font的值）
   * @param {文本的横坐标} x
   */
  drawCenterText({ text, width, x, fontSize }) {
    let textw = this.measureText(text, fontSize);
    let newX = (parseFloat(width) - textw) / 2 + parseFloat(x);
    this.drawText({
      ...options,
      x: newX,
    });
  }

  /**
   * 绘制简单文本
   * @param {文本内容} text
   * @param {左上角的横坐标} x
   * @param {左上角的纵坐标} y
   * @param {文本颜色} color
   * @param {文本大小，默认16px} fontSize（跟font拼接成ctx.font的值）
   * @param {字体样式的属性字符串} font（跟fontSize拼接成ctx.font的值）
   * @param {文本的对齐方式，默认左对齐} textAlign
   * @param {是否有删除线} isThroughLine
   */
  drawText({
    text = "",
    x,
    y,
    color = "black",
    fontSize = 16,
    font = "黑体",
    textAlign = "left",
    isThroughLine = false,
  }) {
    this.ctx.save();
    this.ctx.fillStyle = color;
    this.ctx.textAlign = textAlign;
    this.ctx.font = fontSize + "px " + font; // 'italic bold 20px cursive'
    this.ctx.fillText(text, x, y + fontSize); // 文字的纵坐标要加上字体高度，否则会超出画布

    if (isThroughLine) {
      this.ctx.beginPath();
      this.ctx.lineWidth = 0.5;
      this.ctx.moveTo(x, y + fontSize / 2);
      this.ctx.lineTo(x + this.measureText(text).width, y + fontSize / 2);
      this.ctx.strokeStyle = color;
      this.ctx.stroke();
    }

    this.ctx.restore();
  }

  /**
   * 绘制长文本，并自动换行
   * @param {文本内容} text
   * @param {左上角的横坐标} x
   * @param {左上角的纵坐标} y
   * @param {文本颜色} color
   * @param {文本大小，默认16px} fontSize（跟font拼接成ctx.font的值）
   * @param {字体样式的属性字符串} font（跟fontSize拼接成ctx.font的值）
   * @param {行高} lineHeight
   * @param {行宽，根据行宽自动换行} lineWidth
   * @param {最大文本显示行数，超出部分用“...”表示} maxLine
   */
  drawLongText({text,
    x,
    y,
    color = "black",
    fontSize = 16,
    font = "黑体",
    lineHeight = 22,
    lineWidth,
    maxLine = 4}) {
    this.ctx.save();
    this.ctx.fillStyle = color;
    this.ctx.font = fontSize + "px " + font; // 'italic bold 20px cursive'
    let line = 0;
    let temp = [""];
    let txtArr = (text + "").split("");
    for (let i = 0; i < txtArr.length; i++) {
      if (
        this.measureText(temp[line], fontSize) +
          this.measureText(txtArr[i], fontSize) <=
        lineWidth
      ) {
        temp[line] += txtArr[i] + "";
      } else {
        line += 1;
        temp[line] = txtArr[i] + "";
      }
    }
    for (let i = 0; i < temp.length; i++) {
      if (i < maxLine - 1) {
        this.ctx.fillText(temp[i], x, parseFloat(y) + lineHeight * (i + 1));
      } else if (i == maxLine - 1) {
        let specLine = temp[i].split(" ");
        specLine[specLine.length - 1] = "...";
        let ellipsisLine = specLine.join("");
        this.ctx.fillText(
          ellipsisLine,
          x,
          parseFloat(y) + lineHeight * (i + 1)
        );
      }
    }
    this.ctx.restore();
  }

  /**
   * 画布填充文字，并自动换行
   * @param {头像图片地址} url
   * @param {左上角的横坐标} x
   * @param {左上角的纵坐标} y
   * @param {是否是圆形头像} isRound
   * @param {圆形头像的半径} r
   * @param {圆形头像的外边框颜色} borderColor
   * @param {圆形头像的外边框宽度} borderWidth
   */
  drawAvatar({
    url,
    x,
    y,
    r,
    padding = 0,
    borderColor = "#fff",
    borderWidth = 1,
    isRound = true,
  }) {
    this.ctx.save();
    if (isRound) {
      this.drawCircle({
        x: parseFloat(x) + parseFloat(r + padding),
        y: parseFloat(y) + parseFloat(r + padding),
        r: parseFloat(r),
        sAngel: 0,
        eAngle: 2,
        borderColor,
        borderWidth,
      }); //画圆
      this.ctx.clip(); //画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域，则所有之后的绘图都会被限制在被剪切的区域内。所以要save上下文的原因
    }

    this.drawImage(
      {
        url,
        x,
        y,
        width: 2 * r,
        height: 2 * r,
      },
      false
    );
  }

  /**
   * 绘制直线
   * @param {起始点横坐标} sx
   * @param {起始点纵坐标} sy
   * @param {结束点横坐标} ex
   * @param {结束点纵坐标} ey
   * @param {线条宽度} width
   * @param {线条颜色} color
   * @param {是否闭合} isClosure
   */
  drawLine({
    sx = 0,
    sy = 0,
    ex = 10,
    ey = 10,
    width = 1,
    color = "#000",
    isClosure = false,
  }) {
    this.ctx.save();
    this.ctx.beginPath();
    this.ctx.lineWidth = width;
    this.ctx.strokeStyle = color;
    this.ctx.moveTo(sx, sy);
    this.ctx.lineTo(ex, ey);
    isClosure && this.ctx.closePath();
    this.ctx.stroke(); //绘制路径
    this.ctx.restore();
  }

  clearCanvas() {
    this.ctx && this.ctx.clearRect(0, 0, this.width, this.height);
  }

  /**
   * 绘制各种图形
   * @param {*} type
   * @param {*} options
   */
  drawDispatcher(type = "", options = {}) {
    return new Promise((resolve, reject) => {
      switch (type) {
        case "text":
          this.drawText(options);
          resolve(true);
          break;
        case "longText":
          this.drawLongText(options);
          resolve(true);
          break;
        case "centerText":
          this.drawCenterText(options);
          resolve(true);
          break;
        case "line":
          this.drawLine(options);
          resolve(true);
          break;
        case "rect":
          this.drawRect(options);
          resolve(true);
          break;
        case "circle":
          this.drawCircle(options);
          resolve(true);
          break;
        case "image":
          this.drawImage(options);
          resolve(true);
          break;
        case "avatar":
          this.drawAvatar(options);
          resolve(true);
          break;

        default:
          console.log("不画空气");
          reject(false);
          break;
      }
    });
  }

  /**
   * 执行绘制任务
   * @param {画布宽高，需要绘制的图形列表，是否清理画布} config
   * @param {展示绘画步骤延迟时间} delay
   * @returns
   */
  create(config = {}, delay = 0) {
    config = Object.assign({ shapes: [], isClear: true }, config);
    const { shapes, isClear } = config;
    console.log("绘制配置为===>", config);
    isClear && this.clearCanvas();

    let errList = null;
    return new Promise(async (resolve, reject) => {
      for (let i = 0; i < shapes.length; i++) {
        const { type = "", options = {} } = shapes[i];
        if (delay) {
          await this.sleep(delay);
        }
        try {
          await this.drawDispatcher(type, options);
        } catch (err) {
          console.error(err);
          errList.push({ index: i, err, msg: `第 ${i} 步 绘制失败` });
        }
      }
      resolve(true, errList);
    });
  }

  sleep(ms = 300) {
    return new Promise((resolve) =>
      setTimeout(() => {
        resolve(true);
      }, ms)
    );
  }
}

/**
 * Tips:
 *
 * save() 保存绘图上下文；
 * restore() 恢复之前保存的绘图上下文（save()之前的状态设置代码+默认状态）。
 *
 * beginPath()
 * closePath() 链接begin到close的点，形成一个封闭图形
 */
